#!/usr/bin/python
USE_COMPILED_UI = False
# commands to compile UIs: 
#      pyuic4 gui.ui -o gui_ui.py
#      pyuic4 prefs.ui -o prefs_ui.py
import sys, os
import datetime # for preferences (should this be moved to backend?)
import pdb #python debugger.
import subprocess
from PIL import Image, ImageQt
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QObject, SIGNAL, QThread, pyqtSignal, QMutex, QWaitCondition, QTimer
from PyQt4.QtGui import QPixmap, QIcon, QValidator
from PyQt4.QtGui import (QItemSelectionModel, QListView, QStandardItemModel, QStandardItem)

import imp
# for win compatibility
#requires import imp, os, sys
#returns True when running from the exe
def main_is_frozen():
	return (hasattr(sys, "frozen") or # new py2exe
               hasattr(sys, "importers") # old py2exe
               or imp.is_frozen("__main__")) # tools/freeze

#Returns path of exe
def get_main_dir():
	if main_is_frozen():
		return os.path.dirname(sys.executable)
	return os.path.dirname(sys.argv[0])

if main_is_frozen():
	PROJECT_ROOT = get_main_dir()
else:
	PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
 
sys.path.append(PROJECT_ROOT)

if USE_COMPILED_UI:
	from gui_ui import Ui_MainWindow
	print "Using pre-compiled UI"
else:
	from PyQt4 import uic

import backend as backend
import prefs as prefwindow

class ImportThread(QThread):
	sig_progress = pyqtSignal(str, object, float)
	sig_finished = pyqtSignal(int, str)
	def __init__(self, guiObject, selected_files):
		super(ImportThread, self).__init__()
		self.gui = guiObject
		self.selected_files = selected_files
		self.sig_progress.connect(self.backend_progress)
		self.sig_finished.connect(self.backend_finished)

	# Callback function for backend
	def backend_progress(self, status, image, percent_complete):
		if backend.QUIT_IMPORT: 
			return False
		if image is None:
			self.gui.ui.label_previewArea.clear()
		else:
			#pixmap = QtGui.QPixmap()
			#pixmap.convertFromImage(image)
			pixmap = QPixmap.fromImage(image)
			self.gui.ui.label_previewArea.setPixmap(pixmap)

		self.gui.ui.progressBar.setValue(percent_complete*100)
		self.gui.ui.statusbar.showMessage(status)
		return False
	
	#Callback function for backend finishing
	def backend_finished(self, numFilesImported, errorLog):
		self.gui.setImportNotRunningGUI()
		self.gui.ui.statusbar.showMessage("Completed import")

		successText = "\nImported "+ str(numFilesImported) + " files"
		self.gui.ui.label_previewArea.setText(successText + "\n\n" + errorLog)

		if backend.prefs["deleteMedia"]:
			self.gui.fill_thumb_browser()
		
		#reload icons if there was an error
		if errorLog:
			self.gui.fill_thumb_browser()
		
		return False

	def run(self):
		backend.Import(self.sig_progress, self.sig_finished, self.selected_files)


class BrowserThread(QThread):
	sig_filecount = pyqtSignal(list, bool)
	sig_thumb_file = pyqtSignal(int, str, str, object, int)
	def __init__(self, guiObject):
		super(BrowserThread, self).__init__(guiObject)
		self.gui = guiObject
		self.sig_filecount.connect(self.backend_filecount)
		self.sig_thumb_file.connect(self.backend_thumb_file)

		imagePath = os.path.join(PROJECT_ROOT,"graphics/frame.png")
		imagePath = os.path.normpath(imagePath) # cleans up path and make's it cross-platform
		self.defaultIcon = QIcon(imagePath)

	def backend_filecount(self, fileSlice, isComplete):
		self.gui.liststore_files.extend(fileSlice)
		for f in fileSlice:
			item = QStandardItem(self.defaultIcon, os.path.split(str(f))[1])
			self.gui.filelist_model.appendRow(item)

		if isComplete:
			self.gui.fileBrowseCount = len(self.gui.liststore_files)
			self.gui.set_status_total_filecount()

	def backend_thumb_file(self, restartCount, fullpath, name, image, index):
		#Check if an update from an old thread
		if self.gui.numberBrowserThreadRestarts != restartCount:
			return

		if image is None or image.isNull():
			return
		#try:
		if self.gui.liststore_files[index] == fullpath:
			standardItem = self.gui.filelist_model.item(index)
			pixmap = QPixmap.fromImage(image)
			#pixmap = QtGui.QPixmap()
			#pixmap.convertFromImage(image)
			icon = QIcon(pixmap) 
			#icon = QIcon(QPixmap.fromImage(ImageQt.ImageQt(image)))
			if standardItem: standardItem.setIcon(icon)
		#except:
		#	print "skipping thumb for ", fullpath

	def run(self):
		backend.getBrowserFiles(0, self.sig_filecount, self.sig_thumb_file)

class ThumbnailThread(QThread):
	sig_info = pyqtSignal(str)
	sig_thumb = pyqtSignal(object)
	def __init__(self, guiObject, filepath):
		super(ThumbnailThread, self).__init__()
		self.gui = guiObject
		self.filepath = filepath
		self.waitCondition = QWaitCondition()
		self.mutex = QMutex()
		self.sig_info.connect(self.backend_info)
		self.sig_thumb.connect(self.backend_thumb)	
	
	def setpath(self, filepath):
		self.filepath = filepath
		
	def backend_info(self, infoString):
		self.gui.ui.label_metadata.setText(infoString)
		#TODO do we want this? Adds a momentary "flicker" 
		self.gui.ui.label_previewArea.clear()
		self.gui.ui.label_previewArea.setText("Loading image...")
		#**************

	def backend_thumb(self, image):
		if image is not None:
			pixmap = QPixmap.fromImage(image)
			#pixmap = QtGui.QPixmap()
			#pixmap.convertFromImage(image)
			self.gui.ui.label_previewArea.setPixmap(pixmap)
		else:
			self.gui.ui.label_previewArea.clear()
			self.gui.ui.label_previewArea.setVisible(False)

	def run(self):
		while True:
			self.mutex.lock()
			h = self.gui.geometry().height()-400 #Rough hack/approximation of height
			if h>50:
				backend.getThumbAndInfo(self.filepath, self.sig_info, self.sig_thumb, maxHeight=h)
			else:
				self.gui.ui.label_previewArea.setText("Image won't fit")
			self.waitCondition.wait(self.mutex)
			self.mutex.unlock()

class GUI(QtGui.QMainWindow):
	# Main Window

	def timeInterval(self):
		self.resizeHack=False
	
	def closeEvent(self, event):
		self.clean_up()

	def resizeEvent(self, event):
		if self.resizeHack is False:
			self.resizeHack = True
			self.filelist_browser_selection_changed()

	def on_button_import_released(self):
		self.setImportRunningGUI()
		self.ui.label_previewArea.setVisible(True)
		
		selected_files = self.getSelectedFiles()

		self.importThread = ImportThread(self, selected_files);
		self.importThread.start()

	def on_button_close_released(self):
		if self.ImportRunning is False:
			self.clean_up()
			sys.exit()
		else:
			backend.QUIT_IMPORT = True
			self.setImportNotRunningGUI()
			self.ui.statusbar.showMessage("Canceled import")

	
	def on_button_show_options_toggled(self, showOptions):
		self.ui.frame_browser.setVisible(showOptions)
		self.ui.frame_options.setVisible(showOptions)
		self.ui.menubar.setVisible(showOptions)
		if showOptions:
			self.ui.button_show_options.setText("Hide Options")
			self.ui.label_previewArea.setText("")
			if self.fill_thumb_browser_has_run is False:
				self.fill_thumb_browser()
			self.ui.label_previewArea.setVisible(False)	
		else: #Hide
			self.set_status_total_filecount()
			self.ui.label_previewArea.clear()
			#self.setMaximumSize(350,350)
			#self.resize(350,350)
			self.ui.widget_metadata.hide()
			self.ui.button_show_options.setText("Show Options")
			
			self.ui.label_previewArea.setVisible(False) #needed for correct resizing
			self.updateHideOptionsText()
			#TODO: set (x,y) to correct value
			self.setGeometry(700,300,300,300) # (x,y,w,h)
		
	def updateHideOptionsText(self):
		stat = "\nSource:\n" + backend.prefs["sourceDir"] + "\n\nDestination:\n" + backend.prefs["photoLibraryPath"]
		if backend.prefs["deleteMedia"]:
			stat += "\n\nDelete files from device after successful import"
		else:
			stat += "\n\nLeave photos on device after import"
		self.ui.label_previewArea.setText(stat)
		self.ui.label_previewArea.setVisible(True)

	#-------------------------------
	# Main Options: Check Boxes, file choosers, rename text entry box
	def on_button_browseDevice_released(self):
		qpath = QtGui.QFileDialog.getExistingDirectory(self, self.tr('Select the Camera or Device'), backend.prefs["sourceDir"])
		if not qpath: return
		path = os.path.normpath(str(qpath))
		if backend.prefs["sourceDir"] != path:
			backend.prefs["sourceDir"] = path
			self.ui.label_source_device.setText(path)
			self.fill_thumb_browser()
		
	def on_check_import_photos_toggled(self, value):
		backend.prefs["importPhotos"] = value
		if self.fill_thumb_browser_has_run:
			#Don't fill browser if browser still hidden
			self.fill_thumb_browser()
		
	def on_check_import_videos_toggled(self, value):
		backend.prefs["importVideos"] = value
		if self.fill_thumb_browser_has_run:
			self.fill_thumb_browser()
		
	def on_check_delete_files_toggled(self, value):
		backend.prefs["deleteMedia"] = value
		
	def on_check_rename_toggled(self, isChecked):
		backend.prefs["rename"] = isChecked
		self.ui.lineEdit_rename.setVisible(isChecked)
		#TODO self.hbox_subDir_label.set_visible(self.chk_rename.get_active() or self.chk_subDir.get_active()) # show dir path example label
		if not isChecked:
			self.ui.lineEdit_rename.setText("")
			backend.prefs["renameString"] = ""
		#TODO self.prefsUpdatePathLabels() #update label text
	
	def on_check_subDir_toggled(self, isChecked):
		self.ui.lineEdit_subDir.setVisible(isChecked) # show text entry box
		#TODO self.hbox_subDir_label.set_visible(self.chk_rename.get_active() or self.chk_subDir.get_active()) # show dir path example label
		#TODO self.on_entry_subDirName_changed(None) #set label text
		if not isChecked:
			backend.GROUP_SUBDIR_NAME = None
		#TODO self.prefsUpdatePathLabels() #update label text

	# TODO: allows blank rename string (if someone wants to rename to just img seq numbers)
	def on_lineEdit_rename_textEdited(self, value):
		backend.prefs["renameString"] = str(value)
		#TODO self.prefsUpdatePathLabels() #update label text
		#TODO textBox.set_text(name) # sets the text in gui to the clean filename (without illegal chars)
	
	def on_lineEdit_subDir_textEdited(self, value):
		backend.GROUP_SUBDIR_NAME = str(value)
		#TODO self.entry_subDirName.set_text(dirName) #sets gui to clean char (no illegal chars)
		#TODO self.prefsUpdatePathLabels()
		
	def on_button_metadata_toggled(self, value):
		if value:
			self.ui.button_metadata.setArrowType(2)
			self.ui.label_metadata.show()
		else:
			self.ui.button_metadata.setArrowType(4)
			self.ui.label_metadata.hide()
	
	#-------------------------------
   # [End: Check Boxes, file choosers, rename text entry box]


	#-------------------------------
	# Icon Viewer:
	def filelist_browser_selection_changed(self, newly_selected=None, newly_deselected=None):
		selected = self.ui.filelist_browser.selectedIndexes()
		if len(selected) == 1:
			filepath = self.liststore_files[selected[0].row()]
			if self.thumbThread:
				self.thumbThread.setpath(filepath)
				self.thumbThread.waitCondition.wakeAll()				
			else:
				self.thumbThread = ThumbnailThread(self, filepath);
				self.thumbThread.start()
			self.ui.label_previewArea.setVisible(True)
			self.ui.widget_metadata.show()
		else: #nothing selected
			self.ui.label_previewArea.clear()
			self.ui.label_previewArea.setVisible(False)
			self.ui.label_metadata.setText("")
			self.ui.widget_metadata.hide()

		if len(selected) > 0:
			self.ui.statusbar.showMessage(str(len(selected)) + " files selected")
			self.ui.widget_delete.show()
		else:
			self.set_status_total_filecount()
			self.ui.widget_delete.hide()
	
	def on_filelist_browser_doubleClicked(self, modelIndex):
		path = self.liststore_files[modelIndex.row()]
		try: #linux
			subprocess.Popen(["xdg-open", path])
		except:
			try: #Windows
				os.startfile(path)
			except:
				pass
	#-------------------------------[End: Icon Viewer]


	#-------------------------------
	# Top Menu Bar:
	def on_actionQuit_triggered(self, checked=None):
		if checked is None: return
		self.clean_up()
		sys.exit()

	def on_actionSave_Preferences_triggered(self, checked=None):
		if checked is None: return
		successfulSave = backend.savePrefs()
		self.ui.label_previewArea.setVisible(True)
		if successfulSave:
			m="Saved preferences"
		else:
			m="Error: failed to save"
		self.ui.label_previewArea.setText(m)
		self.ui.statusbar.showMessage(m, 2000)

	def on_actionPreferences_triggered(self, checked=None):
		if checked is None: return
		win = prefwindow.PrefWindow(backend, PROJECT_ROOT, USE_COMPILED_UI)
		win.show()
		win.exec_()
	
	#def on_menuitem_about_activate(self, menuitem):
	#	self.aboutdialog.run()
	#-------------------------------[End: Top Menu Bar]
	
	#-------------------------------
	# About Dialog
	
	#close the dialog box. not sure what the integer does, but it's needed
	#def on_aboutdialog_close(self, widget, integer):
	#	widget.hide()
	#-------------------------------[End: About Dialog]
	
	#-------------------------------
	# Delete photos Buttons
	def on_button_delete1_released(self):	
		selected = self.ui.filelist_browser.selectedIndexes()
		if len(selected) <= 0:
			self.ui.statusbar.showMessage("Select files to delete")			
			return

		reply = QtGui.QMessageBox.question(self, 'Message',
			"Are you sure you want to delete "+str(len(selected))+" files?", QtGui.QMessageBox.Yes | 
			QtGui.QMessageBox.No, QtGui.QMessageBox.No)

		if reply == QtGui.QMessageBox.Yes:
			errorfiles = backend.deleteFiles(self.getSelectedFiles())
			if len(errorfiles) > 0:
				self.ui.label_previewArea.setText("Error deleting:" + "\n".join(errorfiles))
			self.fill_thumb_browser()
			self.set_status_total_filecount()
	
	#-------------------------------
	# Helper functions
	
	def set_status_total_filecount(self):
		if self.fileBrowseCount is None:
			txt="Finding files..."
			self.ui.button_import.setEnabled(False)				
		elif self.fileBrowseCount == 0:
			txt="No files found"
			self.ui.button_import.setEnabled(False)
		else:
			s = ""
			if self.fileBrowseCount >= backend.MAX_FILES_IN_THUMB_BROWSER:
				s = "more than "
			txt="Ready to import "+s+str(self.fileBrowseCount)+" files"
			self.ui.button_import.setEnabled(True)
		self.ui.statusbar.showMessage(txt)

	# returns a list of paths for the selected thumbnails
	def getSelectedFiles(self):
		filelist = []
		if not self.ui.button_show_options.isChecked():
			return filelist

		modelIndexList = self.ui.filelist_browser.selectedIndexes()
		if len(modelIndexList) > 0:
			for modelIndex in modelIndexList:
				f = self.liststore_files[modelIndex.row()]
				filelist.append(f)
		return filelist

	def fill_thumb_browser(self):
		self.fill_thumb_browser_has_run = True
		self.filelist_model.clear()
		self.liststore_files = []
		self.ui.statusbar.showMessage("Finding files...");

		if self.browserThread and backend.FILE_BROWSE_RUNNING:
			self.numberBrowserThreadRestarts += 1
			backend.RESTART_THUMBS = True
		else:
			backend.FILE_BROWSE_RUNNING = True
			self.numberBrowserThreadRestarts = 0
			self.browserThread = BrowserThread(self);
			self.browserThread.start()

	def setImportRunningGUI(self):
		self.ImportRunning = True
		self.ui.button_import.setEnabled(False)
		self.ui.button_close.setText("Cancel")
		#TODO self.expander_metadata.hide()

	def setImportNotRunningGUI(self):
		self.ImportRunning = False
		self.ui.button_close.setText("Done")
		self.ui.button_import.setEnabled(True)
		self.ui.progressBar.setValue(0)

	def clean_up(self):
		backend.QUIT_IMPORT = True
		backend.QUIT_THUMBS = True
	
	
	'''
	def photoLibraryPath(self, date=datetime.date.today()):
		path = os.path.join(backend.prefs["photoLibraryPath"])
		if self.prefs_chk_year.get_active():
			path = os.path.join(path, date.strftime("%Y"))
		try:
			path = os.path.join(path, date.strftime(self.prefs_entry_monthDir.get_text()))
		except:
			return ""
		if backend.GROUP_SUBDIR_NAME:
			path = os.path.join(path, backend.GROUP_SUBDIR_NAME)
		return path
		
		# for updating filename labels in the gui
	def labelFileName(self, name="photo", num = 1):
		string = name + self.prefs_entry_paddingSeparator.get_text()
		string += str(num).zfill(int(self.prefs_entry_numPadding.get_text()))
		string += '.' + backend.prefs["photoExtensions"][0] + '"' #uses first extension in prefs as example
		return string
	'''
	# called while filename is being modified
	def makeLegalFilename(self, name):
		badchars = '\/:*?^"<>|'
		badNames = ('CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4',
					'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2',
					'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9')
					
		for c in badchars:
			name = name.replace(c,'')
		for s in badNames:
			if name.upper() == s: name = ''
		return name
		
	def removeIllegalChars(self,  string):
		badchars = '\/:*?^"<>|'
		for c in badchars:
			name = name.replace(c,'')
		return name
	'''	
	# called after filename is commited (leave focus, or press enter)
	# removes leading/trailing whitespace and any trailing '.'
	def legalFilenameHelper(self, name):
		name = name.strip() #removes leading and trailing whitespace
		while name.endswith(' ') or name.endswith('.'):
			name = name.rstrip('.')
			name = name.rstrip(' ')
		return name
			
	# Only for debugging
	def printPrefs(self, button):
		print "SubDir\t", backend.GROUP_SUBDIR_NAME
		for key, value in backend.prefs.items():
			print key, "\t", value
		print "==================================\n"
	'''	
	# Initialization	
	def set_variables(self):
		if len(sys.argv) > 1 and os.path.isdir(sys.argv[1]):
			backend.prefs["sourceDir"] = sys.argv[1]
			fList = os.listdir(sys.argv[1])
			if "Android" in fList:
				if "DCIM" in fList:
					#Special check for Android devices
					backend.prefs["sourceDir"] = os.path.join(sys.argv[1],"DCIM")
				elif "dcim" in fList:
					backend.prefs["sourceDir"] = os.path.join(sys.argv[1],"dcim")
				elif "Dcim" in fList:
					backend.prefs["sourceDir"] = os.path.join(sys.argv[1],"Dcim")
		else:
			backend.findCamera()
			backend.verifyPaths()
		self.photo_library_firsttime = True
		self.photo_source_firsttime = True
		self.browserThread = None
		self.fileBrowseCount = None
		self.thumbThread = None
		self.ImportRunning = False
		self.fill_thumb_browser_has_run = False
		self.timer = QTimer();
		self.timer.setInterval(1000)
		self.connect(self.timer, SIGNAL("timeout()"), self.timeInterval);
		self.timer.start();
		self.resizeHack = True
	
	def __init__(self, parent=None):
		QtGui.QMainWindow.__init__(self, parent)
		if USE_COMPILED_UI:
			self.ui = Ui_MainWindow()
		else:
			ui_class, widget_class = uic.loadUiType(os.path.join(PROJECT_ROOT,"gui.ui"))
			self.ui = ui_class()
		self.ui.setupUi(self)

		self.set_variables()
		
		self.filelist_model = QStandardItemModel()
		self.ui.filelist_browser.setModel(self.filelist_model)
		self.connect(self.ui.filelist_browser.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.filelist_browser_selection_changed)

		self.on_button_show_options_toggled(False) #Hide options and menu
		
		#Set Main Window settings:
		list=backend.findFiles(backend.prefs["sourceDir"], justOne=True)
		if list and len(list) > 0:
			self.ui.statusbar.showMessage("Ready to import to: "+backend.prefs["photoLibraryPath"])
			self.ui.button_import.setEnabled(True)
		else:
			self.ui.statusbar.showMessage("Nothing to import")
			self.ui.button_import.setEnabled(False)

		self.updateHideOptionsText()	

		# set global widgets options
		self.ui.label_source_device.setText(backend.prefs["sourceDir"])
		#self.photo_library_browser.set_current_folder(backend.prefs["photoLibraryPath"])
	
		# set user input validators
		v = backend.FileNameValidator()
		self.ui.lineEdit_rename.setValidator(v)
		self.ui.lineEdit_subDir.setValidator(v)

		self.ui.widget_delete.hide()
		self.ui.button_deleteConfirm.hide()		
		self.ui.lineEdit_rename.setVisible(False)	
		self.ui.lineEdit_subDir.setVisible(False)
		self.ui.widget_metadata.setVisible(False)
		self.ui.label_metadata.setVisible(False)	
		self.ui.check_import_photos.setChecked(backend.prefs["importPhotos"])
		self.ui.check_import_videos.setChecked(backend.prefs["importVideos"])
		self.ui.check_delete_files.setChecked(backend.prefs["deleteMedia"])
		self.ui.check_rename.setChecked(backend.prefs["rename"])
		self.ui.lineEdit_rename.setText(backend.prefs["renameString"])
		self.ui.lineEdit_subDir.setText(backend.GROUP_SUBDIR_NAME or "")
		#self.ui.label_previewArea.setFrameStyle(0)

		# set to be the center of the screen
		s = QtGui.QDesktopWidget().screenGeometry()
		w = 300
		h = 300
		self.setGeometry((s.width()-w)/2,(s.height()-h)/2,w,h) # (x,y,w,h)

if __name__ == "__main__":		
	app = QtGui.QApplication(sys.argv)
	win = GUI()
	win.show()
	sys.exit(app.exec_())	
	

